package hotnet.filter;

import hotnet.buffer.IoBuffer;
import hotnet.filter.IoFilter.NextFilter;
import hotnet.future.CloseFuture;
import hotnet.future.ConnectFuture;
import hotnet.future.WriteFuture;
import hotnet.session.AbstractIoSession;
import hotnet.session.DefaultIoSessionAttributeMap;
import hotnet.session.IoSession;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class DefaultIoFilterChain implements IoFilterChain {

	private final static Logger logger = LoggerFactory.getLogger(DefaultIoFilterChain.class);
    private final IoSession session;
    private Map<String, EntryImpl> entryMap = new ConcurrentHashMap<String, EntryImpl>();
    private EntryImpl head;
    private EntryImpl tail;

    public DefaultIoFilterChain(IoSession session) {
		if (session == null) {
			throw  new IllegalArgumentException("session is null");
		}

		this.session = session;
		this.head = new EntryImpl(null, null, new HeadFilter("head"));
		this.tail = new EntryImpl(this.head, null, new TailFilter("tail"));

		this.head.next = this.tail;
	}

	public IoSession getSession() {
    	return this.session;
	}

	private void register(EntryImpl prev, IoFilter filter) {
    	EntryImpl newEntry = new EntryImpl(prev, prev.next, filter);

    	prev.next.prev = newEntry;
    	prev.next = newEntry;

    	entryMap.put(filter.getName(), newEntry);
	}

	private  void deregister(EntryImpl entry) {
		EntryImpl prev = entry.prev;
		EntryImpl next = entry.next;

		prev.next = next;
		next.prev = prev;

		entryMap.remove(entry.getFilter().getName());
	}

	private EntryImpl getEntry(String name) {
    	EntryImpl e = entryMap.get(name);

    	if (e == null) {
    		throw new IllegalArgumentException("filter not found: " + name);
		}

		return e;
	}

	private void checkValidable(String name) {
		if (entryMap.get(name) == null) {
			throw new IllegalArgumentException("other filter uses the same name :" + name);
		}
	}

	@Override
	public synchronized void addFirst(IoFilter filter) {
		register(this.head, filter);
	}

	@Override
	public synchronized void addLast(IoFilter filter) {
		register(this.tail.prev, filter);
	}

	@Override
	public synchronized void addBefore(String baseName, IoFilter filter) {
		EntryImpl entry = getEntry(baseName);

		register(entry.prev, filter);
	}

	@Override
	public synchronized void addAfter(String baseName, IoFilter filter) {
		EntryImpl entry = getEntry(baseName);

		register(entry, filter);
	}

	@Override
	public synchronized List<Entry> getAll() {
		List<Entry> list = new ArrayList<Entry>();
		EntryImpl e = head.next;

		while (e != tail) {
			list.add(e);

			e = e.next;
		}

		return list;
	}
	
	@Override
	public synchronized List<Entry> getAllReverse() {
		List<Entry> list = new ArrayList<Entry>();
		EntryImpl e = tail.prev;

		while (e != head) {
			list.add(e);

			e = e.prev;
		}

		return list;
	}

	@Override
	public IoFilter remove(String name) {
		EntryImpl entry = getEntry(name);
		deregister(entry);
		return entry.getFilter();
	}

	@Override
	public void clear() throws Exception {
        List<EntryImpl> l = new ArrayList<EntryImpl>(entryMap.values());

        for (EntryImpl e : l) {
        	try {
        		deregister(e);
			} catch (Exception x) {
				throw new IllegalArgumentException("clear(): " + e.getFilter().getName() + " in " + getSession());
			}
		}
	}

	@Override
	public void fireSessionCreated() {
		logger.debug("session:" + session.getSessionId() + " fireSessionCreated");
        callNextSesssionCreated(head, session);
	}

	private void callNextSesssionCreated(Entry entry, IoSession session) {
		try {
		    IoFilter filter = entry.getFilter();
			NextFilter nextFilter = entry.getNextFilter();
			
			logger.debug("session:" + session.getSessionId() + " filter:" + filter.getName() + " sessionCreated!");

            filter.sessionCreated(nextFilter, session);
		} catch (Exception e) {
			fireExceptionCaught(e);
		} catch (Error e) {
			fireExceptionCaught(e);
			throw e;
		}
	}

	@Override
	public void fireSessionOpened() {
		logger.debug("session:" + session.getSessionId() + " fireSessionOpened");

        callNextSesssionOpened(head, session);
	}

	private void callNextSesssionOpened(Entry entry, IoSession session) {
		try {
            IoFilter filter = entry.getFilter();
			NextFilter nextFilter = entry.getNextFilter();
			logger.debug("session:" + session.getSessionId() + " filter:" + filter.getName() + " sessionOpened!");

            filter.sessionOpened(nextFilter, session);
		} catch (Exception e) {
			fireExceptionCaught(e);
		} catch (Error e) {
			fireExceptionCaught(e);
			throw e;
		}
	}

	@Override
	public void fireSessionClosed() {
        /* update future state */
        AbstractIoSession abstractIoSession = (AbstractIoSession)session;
        CloseFuture future = abstractIoSession.getCloseFuture();
        future.setClosed();
		logger.debug("session:" + session.getSessionId() + " fireSessionClosed");

        callNextSesssionClosed(head, session);
	}

	private void callNextSesssionClosed(Entry entry, IoSession session) {
		try {
            IoFilter filter = entry.getFilter();
			NextFilter nextFilter = entry.getNextFilter();
			logger.debug("session:" + session.getSessionId() + " filter:" + filter.getName() + " sessionClosed!");

            filter.sessionClosed(nextFilter, session);
		} catch (Exception e) {
			fireExceptionCaught(e);
		} catch (Error e) {
			fireExceptionCaught(e);
			throw e;
		}
	}

	@Override
	public void fireMessageReceived(Object message) {
		logger.debug("session:" + session.getSessionId() + " fireMessageReceived");

        callNextMessageReceived(head, session, message);
	}

	private void callNextMessageReceived(Entry entry, IoSession session, Object message) {
		try {
            IoFilter filter = entry.getFilter();
			NextFilter nextFilter = entry.getNextFilter();
			logger.debug("session:" + session.getSessionId() + " filter:" + filter.getName() + " messageReceived!");

            filter.messageReceived(nextFilter, session, message);
		} catch (Exception e) {
			fireExceptionCaught(e);
		} catch (Error e) {
			fireExceptionCaught(e);
			throw e;
		}
	}

	@Override
	public void fireMessageSent(WriteFuture request) {
		logger.debug("session:" + session.getSessionId() + " fireMessageSent");

        callNextMessageSent(head, session, request);
	}

	private void callNextMessageSent(Entry entry, IoSession session, WriteFuture request) {
		try {
            IoFilter filter = entry.getFilter();
			NextFilter nextFilter = entry.getNextFilter();
			logger.debug("session:" + session.getSessionId() + " filter:" + filter.getName() + " messageSent!");

			filter.messageSent(nextFilter, session, request);
		} catch (Exception e) {
			fireExceptionCaught(e);
		} catch (Error e) {
			fireExceptionCaught(e);
			throw e;
		}
	}

	@Override
	public void fireExceptionCaught(Throwable cause) {
		logger.debug("session:" + session.getSessionId() + " fireExceptionCaught");

        callNextExceptionCaught(head, session, cause);
	}

	private void callNextExceptionCaught(Entry entry, IoSession session, Throwable cause) {
		ConnectFuture future = (ConnectFuture) session.removeAttribute(
				DefaultIoSessionAttributeMap.CONNECT_FUTURE);

		if (future == null) {
			try {
                IoFilter filter = entry.getFilter();
				NextFilter nextFilter = entry.getNextFilter();
				logger.debug("session:" + session.getSessionId() + " filter:" + filter.getName() + " exceptionCaught!");

                filter.exceptionCaught(nextFilter, session, cause);
			} catch (Throwable e) {
				logger.warn("Unexpected exception from exceptionCaught handler.", e);
			}
		} else {
			session.close(true);
			future.setException(cause);
		}

	}

	@Override
	public void fireFilterClose() {
		logger.debug("session:" + session.getSessionId() + " fireFilterClose");

        callPreviousFilterClose(tail, session);
	}

	private void callPreviousFilterClose(Entry entry, IoSession session) {
		try {
		    IoFilter filter = entry.getFilter();
			NextFilter nextFilter = entry.getNextFilter();
			logger.debug("session:" + session.getSessionId() + " filter:" + filter.getName() + " filterClose!");

            filter.filterClose(nextFilter, session);
		} catch (Exception e) {
			fireExceptionCaught(e);
		} catch (Error e) {
			fireExceptionCaught(e);
			throw e;
		}
	}

	@Override
	public void fireInputClosed() {
		logger.debug("session:" + session.getSessionId() + " fireInputClosed");

        callNextInputClosed(head, session);
	}

	private void callNextInputClosed(Entry entry, IoSession session) {
		try {
            IoFilter filter = entry.getFilter();
			NextFilter nextFilter = entry.getNextFilter();
			logger.debug("session:" + session.getSessionId() + " filter:" + filter.getName() + " inputClosed!");

            filter.inputClosed(nextFilter, session);
		} catch (Exception e) {
			fireExceptionCaught(e);
		}
	}

	@Override
	public void fireFilterWrite(WriteFuture writeFuture) {
		logger.debug("session:" + session.getSessionId() + " fireFilterWrite");

        callPreviousFilterWrite(tail, session, writeFuture);
	}

	private void callPreviousFilterWrite(Entry entry, IoSession session, WriteFuture writeFuture) {
		try {
            IoFilter filter = entry.getFilter();
			NextFilter nextFilter = entry.getNextFilter();
			logger.debug("session:" + session.getSessionId() + " filter:" + filter.getName() + " filterWrite!");

            filter.filterWrite(nextFilter, session, writeFuture);
		} catch (Exception e) {
			writeFuture.setException(e);
			fireExceptionCaught(e);
		} catch (Error e) {
			writeFuture.setException(e);
			fireExceptionCaught(e);
			throw e;
		}
	}

	private class HeadFilter extends IoFilterAdapter {
		public HeadFilter(String name) {
			super(name);
		}

		@Override
		public void filterClose(NextFilter nextFilter, IoSession session) throws Exception {
			session.getProcessor().remove(session);
		}

		@Override
		public void filterWrite(NextFilter nextFilter, IoSession session, WriteFuture writeFuture) throws Exception { 
			// Maintain counters.
			if (writeFuture.getMessage() instanceof IoBuffer) {
				IoBuffer buffer = (IoBuffer) writeFuture.getMessage();
				// I/O processor implementation will call buffer.reset()
				// it after the write operation is finished, because
				// the buffer will be specified with messageSent event.
				buffer.mark();
			}

			session.getProcessor().write(session, writeFuture);
		}
	}

	private class TailFilter extends IoFilterAdapter {
		public TailFilter(String name) {
			super(name);
		}

		@Override
		public void sessionCreated(NextFilter nextFilter, IoSession session) throws Exception {
			try {
				session.getHandler().sessionCreated(session);
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}

		@Override
		public void sessionOpened(NextFilter nextFilter, IoSession session) throws Exception {
			try {
				session.getHandler().sessionOpened(session);
			} catch(Exception e) {
				
			}
			finally {
				// Notify the related future.
				ConnectFuture future = (ConnectFuture) session.removeAttribute(
						DefaultIoSessionAttributeMap.CONNECT_FUTURE);

				if (future != null) {
					future.completeConnect(session);
				}
			}
		}

		@Override
		public void sessionClosed(NextFilter nextFilter, IoSession session) throws Exception {
			try {
				session.getHandler().sessionClosed(session);
			} catch(Exception e) {
			} finally {
				try {
					session.getFilterChain().clear();
				} catch (Exception e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}

		@Override
		public void exceptionCaught(NextFilter nextFilter, IoSession session, Throwable cause) throws Exception {
			try {
				try {
					session.getHandler().exceptionCaught(session, cause);
				} catch (Exception e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			} finally {

			}
		}

		@Override
		public void messageReceived(NextFilter nextFilter, IoSession session, Object message) throws Exception {

			// Propagate the message
			try {
				try {
					session.getHandler().messageReceived(session, message);
				} catch (Exception e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			} finally {

			}
		}

		@Override
		public void messageSent(NextFilter nextFilter, IoSession session, WriteFuture writeFuture) {
			// Propagate the message
			try {
				session.getHandler().messageSent(session, writeFuture.getMessage());
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}

		@Override
		public void filterWrite(NextFilter nextFilter, IoSession session, WriteFuture writeFuture) {
			nextFilter.filterWrite(session, writeFuture);
		}

		@Override
		public void filterClose(NextFilter nextFilter, IoSession session) {
			nextFilter.filterClose(session);
		}

		@Override
		public void inputClosed(NextFilter nextFilter, IoSession session) {
			try {
				session.getHandler().inputClosed(session);
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}

	}

	@Override
	public String toString() {
        EntryImpl entry = head;
        boolean isFirst = true;
        StringBuffer buffer = new StringBuffer("filter:{");

        while (entry != null) {
            if (!isFirst) {
                buffer.append(",");
            }

            buffer.append(entry.getFilter().getName());
            buffer.append(":");
            buffer.append(entry.getFilter().getClass().getCanonicalName());

            isFirst = false;

            entry = entry.next;
        }
        buffer.append("}");
        
        return buffer.toString();
    }

	final class EntryImpl implements Entry {
    	private IoFilter filter;
    	// nextfilter 閫氳繃prev 鍜�next 瀹炵幇filter鐨勯『搴�
    	private final NextFilter nextFilter;
    	private EntryImpl prev;
    	private EntryImpl next;

    	public EntryImpl(EntryImpl prev, EntryImpl next, IoFilter filter) {
			if (filter == null) {
				throw new IllegalArgumentException("filter is nulll");
			}

			this.prev = prev;
			this.next = next;
			this.filter = filter;

			this.nextFilter = new NextFilter() {
				@Override
				public void sessionCreated(IoSession session) {
					Entry nextEntry = EntryImpl.this.next;
					callNextSesssionCreated(nextEntry, session);
				}

				@Override
				public void sessionOpened(IoSession session) {
					Entry nextEntry = EntryImpl.this.next;
					callNextSesssionOpened(nextEntry, session);
				}

				@Override
				public void sessionClosed(IoSession session) {
					Entry nextEntry = EntryImpl.this.next;
					callNextSesssionClosed(nextEntry, session);
				}

				@Override
				public void messageReceived(IoSession session, Object message) {
					Entry nextEntry = EntryImpl.this.next;
					callNextMessageReceived(nextEntry, session, message);
				}

				@Override
				public void messageSent(IoSession session, WriteFuture future) {
					Entry nextEntry = EntryImpl.this.next;
					callNextMessageSent(nextEntry, session, future);
				}

				@Override
				public void inputClosed(IoSession session)  {
					Entry nextEntry = EntryImpl.this.next;
					callNextInputClosed(nextEntry, session);
				}

				@Override
				public void filterClose(IoSession session) {
					Entry nextEntry = EntryImpl.this.prev;
					callPreviousFilterClose(nextEntry, session);
				}

				@Override
				public void filterWrite(IoSession session, WriteFuture future) {
					Entry nextEntry = EntryImpl.this.prev;
					callPreviousFilterWrite(nextEntry, session, future);
				}

				@Override
				public void exceptionCaught(IoSession session, Throwable cause) {
					Entry nextEntry = EntryImpl.this.next;
					callNextExceptionCaught(nextEntry, session, cause);
				}
			};
		}

		@Override
		public IoFilter getFilter() {
			return this.filter;
		}

		@Override
		public NextFilter getNextFilter() {
    		return this.nextFilter;
		}

		@Override
		public void addAfter(IoFilter filter) {
			DefaultIoFilterChain.this.addAfter(this.getFilter().getName(), filter);
		}

		@Override
		public void addBefore(IoFilter filter) {
			DefaultIoFilterChain.this.addBefore(this.getFilter().getName(), filter);
		}

		@Override
		public void remove() {
			DefaultIoFilterChain.this.remove(this.getFilter().getName());
		}
	}

}
